package wordit.impl.boardwise;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import wordit.Letter;
import wordit.LetterSequence;
import wordit.ea.AbstractGenome;

public class BoardGenome extends AbstractGenome {

	private final Letter[][] board;
	private final Map<Letter, BoardLocation> locationsMap;

	public BoardGenome(int numOfRows, int numOfColumns) {
		board = new Letter[numOfRows][];
		for (int row = 0; row < numOfRows; ++row)
			board[row] = new Letter[numOfColumns];

		locationsMap = new HashMap<Letter, BoardLocation>();
	}

	public BoardGenome(BoardGenome other) {
		this(other.getRowsCount(), other.getColumnsCount());

		for (int row = 0; row < getRowsCount(); ++row)
			for (int column = 0; column < getColumnsCount(); ++column)
				setLetterAt(row, column, other.getLetterAt(row, column));
		
		locationsMap.putAll(other.locationsMap);
	}

	int getRowsCount() {
		return board.length;
	}

	int getColumnsCount() {
		return (getRowsCount() > 0) ? board[0].length : 0;
	}

	public Letter getLetterAt(int row, int col) {
		return board[row][col];
	}

	public void setLetterAt(int row, int col, Letter l) {
		board[row][col] = l;
		if (l != null)
			locationsMap.put(l, new BoardLocation(row, col));
	}

	public List<BoardLocation> getOccupiedLocations() {
		return new ArrayList<BoardLocation>(locationsMap.values());

		/*
		 * List<BoardLocation> occupiedLocations = new
		 * ArrayList<BoardLocation>();
		 * 
		 * for (int row = 0; row < getRowsCount(); ++row) for (int column = 0;
		 * column < getColumnsCount(); ++column) if (getLetterAt(row, column) !=
		 * null) occupiedLocations.add(new BoardLocation(row, column));
		 * 
		 * return occupiedLocations;
		 */
	}

	public List<BoardLocation> getVacantLocations() {
		List<BoardLocation> vacantLocations = new ArrayList<BoardLocation>();

		for (int row = 0; row < getRowsCount(); ++row)
			for (int column = 0; column < getColumnsCount(); ++column)
				if (getLetterAt(row, column) == null)
					vacantLocations.add(new BoardLocation(row, column));

		return vacantLocations;
	}

	public void moveLetter(BoardLocation from, BoardLocation to) {
		if (!from.equals(to)) {
			if (getLetterAt(to.getRow(), to.getColumn()) != null)
				throw new IllegalArgumentException("location " + to
						+ " is occupied");
			
			setLetterAt(to.getRow(), to.getColumn(), getLetterAt(from.getRow(),
					from.getColumn()));
			setLetterAt(from.getRow(), from.getColumn(), null);
		}
	}

	public Collection<LetterSequence> getSequences() {
		Collection<LetterSequence> sequences = new LinkedList<LetterSequence>();

		for (int row = 0; row < getRowsCount(); ++row)
			sequences.addAll(getSequencesInRow(row));

		for (int column = 0; column < getColumnsCount(); ++column) {
			for (LetterSequence s : getSequencesInColumn(column)) {
				// 1-letter sequences are ignored here, since they were already
				// added as row sequences
				if (s.getLength() > 1)
					sequences.add(s);
			}
		}

		return sequences;
	}

	private Collection<LetterSequence> getSequencesInColumn(int column) {
		Collection<LetterSequence> sequences = new LinkedList<LetterSequence>();

		LetterSequence seq = null;
		for (int row = 0; row < getRowsCount(); ++row) {
			if (getLetterAt(row, column) == null) {
				// nothing at this spot

				// if its because a sequence ended, keep it
				if (seq != null) {
					sequences.add(seq);
					seq = null;
				}
			} else {
				// got a letter

				// if first in sequence - prepare sequence
				if (seq == null) {
					seq = new LetterSequence();
				}

				// anyway, append it
				seq.addLetter(getLetterAt(row, column));
			}
		}

		// any leftovers? keep them
		if (seq != null) {
			sequences.add(seq);
			seq = null;
		}

		return sequences;
	}

	private Collection<LetterSequence> getSequencesInRow(int row) {
		Collection<LetterSequence> sequences = new LinkedList<LetterSequence>();

		LetterSequence seq = null;
		for (int column = 0; column < getColumnsCount(); ++column) {
			if (getLetterAt(row, column) == null) {
				// nothing at this spot

				// if its because a sequence ended, keep it
				if (seq != null) {
					sequences.add(seq);
					seq = null;
				}
			} else {
				// got a letter

				// if first in sequence - prepare sequence
				if (seq == null) {
					seq = new LetterSequence();
				}

				// anyway, append it
				seq.addLetter(getLetterAt(row, column));
			}
		}

		// any leftovers? keep them
		if (seq != null) {
			sequences.add(seq);
			seq = null;
		}

		return sequences;
	}

	public String toString() {
		return "(BoardGenome," + getFitness() + "," + getSequences() + ")";
		//return "(BoardGenome," + locationsMap + ")";
	}

	public boolean containsLetter(Letter l) {
		return locationsMap.containsKey(l);
	}

	public void moveLetter(Letter l, BoardLocation to) {
		BoardLocation from = getLocationOf(l);
		if (from != null)
			moveLetter(from, to);
	}

	BoardLocation getLocationOf(Letter l) {
		return locationsMap.get(l);
	}

	public void setUnusedLetter(Letter l) {
		BoardLocation loc;
		
		loc = getLocationOf(l);
		
		if (loc!=null && getLetterAt(loc.getRow(), loc.getColumn())==l) {
			setLetterAt(loc.getRow(), loc.getColumn(), null);
		}
		
		locationsMap.put(l, null);
	}

	public Collection<Letter> getUnusedLetters() {
		List<Letter> unusedLetters = new ArrayList<Letter>();

		for (Entry<Letter, BoardLocation> e : locationsMap.entrySet()) {
			if (e.getValue()==null) {
				unusedLetters.add(e.getKey());
			}
		}
		
		return unusedLetters;
	}
}
